#include <jni.h>
#include <android/log.h>
#include <string>

#include "libdms.h"
#include "dms_player.h"

#define LOG_TAG "DMS_JNI"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

extern "C" {

// Global reference to Java classes
static jclass gKdmInfoClass = nullptr;
static jclass gMxfInfoClass = nullptr;

// Field IDs for KdmInfo
static jfieldID gKdmInfo_id;
static jfieldID gKdmInfo_recipientSubjectName;
static jfieldID gKdmInfo_cplId;
static jfieldID gKdmInfo_contentTitle;
static jfieldID gKdmInfo_notValidBefore;
static jfieldID gKdmInfo_notValidAfter;
static jfieldID gKdmInfo_sessionCount;
static jfieldID gKdmInfo_remainSessionCount;
static jfieldID gKdmInfo_validateTimeWindowResult;
static jfieldID gKdmInfo_validateRecipientResult;

// Field IDs for MxfInfo
static jfieldID gMxfInfo_width;
static jfieldID gMxfInfo_height;
static jfieldID gMxfInfo_frameRate;
static jfieldID gMxfInfo_duration;
static jfieldID gMxfInfo_codec;
static jfieldID gMxfInfo_isEncrypted;

JNIEXPORT jboolean JNICALL
Java_com_yourcompany_dmsplayer_DmsPlayer_initialize(JNIEnv* env, jobject thiz) {
    // Initialize DMS library
    int result = _dms_library_initialize(DMS_MODE_PLAY, nullptr, true);

    if (result != DMS_RESULT_SUCCESS) {
        LOGE("Failed to initialize DMS library: 0x%08x", result);
        return JNI_FALSE;
    }

    // Create and store native context
    DmsContext* context = new DmsContext();
    context->isInitialized = true;
    context->hasActiveMxf = false;
    context->currentPosition = 0;

    jclass clazz = env->GetObjectClass(thiz);
    jfieldID fieldId = env->GetFieldID(clazz, "nativePtr", "J");
    env->SetLongField(thiz, fieldId, reinterpret_cast<jlong>(context));

    // Cache Java class and field IDs
    if (gKdmInfoClass == nullptr) {
        jclass localKdmInfoClass = env->FindClass("com/yourcompany/dmsplayer/DmsPlayer$KdmInfo");
        gKdmInfoClass = static_cast<jclass>(env->NewGlobalRef(localKdmInfoClass));
        env->DeleteLocalRef(localKdmInfoClass);

        gKdmInfo_id = env->GetFieldID(gKdmInfoClass, "id", "Ljava/lang/String;");
        gKdmInfo_recipientSubjectName = env->GetFieldID(gKdmInfoClass, "recipientSubjectName", "Ljava/lang/String;");
        gKdmInfo_cplId = env->GetFieldID(gKdmInfoClass, "cplId", "Ljava/lang/String;");
        gKdmInfo_contentTitle = env->GetFieldID(gKdmInfoClass, "contentTitle", "Ljava/lang/String;");
        gKdmInfo_notValidBefore = env->GetFieldID(gKdmInfoClass, "notValidBefore", "Ljava/lang/String;");
        gKdmInfo_notValidAfter = env->GetFieldID(gKdmInfoClass, "notValidAfter", "Ljava/lang/String;");
        gKdmInfo_sessionCount = env->GetFieldID(gKdmInfoClass, "sessionCount", "I");
        gKdmInfo_remainSessionCount = env->GetFieldID(gKdmInfoClass, "remainSessionCount", "I");
        gKdmInfo_validateTimeWindowResult = env->GetFieldID(gKdmInfoClass, "validateTimeWindowResult", "I");
        gKdmInfo_validateRecipientResult = env->GetFieldID(gKdmInfoClass, "validateRecipientResult", "I");
    }

    if (gMxfInfoClass == nullptr) {
        jclass localMxfInfoClass = env->FindClass("com/yourcompany/dmsplayer/DmsPlayer$MxfInfo");
        gMxfInfoClass = static_cast<jclass>(env->NewGlobalRef(localMxfInfoClass));
        env->DeleteLocalRef(localMxfInfoClass);

        gMxfInfo_width = env->GetFieldID(gMxfInfoClass, "width", "I");
        gMxfInfo_height = env->GetFieldID(gMxfInfoClass, "height", "I");
        gMxfInfo_frameRate = env->GetFieldID(gMxfInfoClass, "frameRate", "F");
        gMxfInfo_duration = env->GetFieldID(gMxfInfoClass, "duration", "J");
        gMxfInfo_codec = env->GetFieldID(gMxfInfoClass, "codec", "Ljava/lang/String;");
        gMxfInfo_isEncrypted = env->GetFieldID(gMxfInfoClass, "isEncrypted", "Z");
    }

    return JNI_TRUE;
}

JNIEXPORT void JNICALL
Java_com_yourcompany_dmsplayer_DmsPlayer_uninitialize(JNIEnv* env, jobject thiz) {
jclass clazz = env->GetObjectClass(thiz);
jfieldID fieldId = env->GetFieldID(clazz, "nativePtr", "J");
DmsContext* context = reinterpret_cast<DmsContext*>(env->GetLongField(thiz, fieldId));

if (context != nullptr) {
if (context->hasActiveMxf) {
_dms_close_dcp(); // Use DCP close function for now
}

_dms_library_uninitialize();
delete context;
env->SetLongField(thiz, fieldId, 0);
}

// Release global references
if (gKdmInfoClass != nullptr) {
env->DeleteGlobalRef(gKdmInfoClass);
gKdmInfoClass = nullptr;
}

if (gMxfInfoClass != nullptr) {
env->DeleteGlobalRef(gMxfInfoClass);
gMxfInfoClass = nullptr;
}
}

JNIEXPORT jobject JNICALL
        Java_com_yourcompany_dmsplayer_DmsPlayer_validateKdm(JNIEnv* env, jobject thiz, jstring kdm_path) {
const char* kdmPath = env->GetStringUTFChars(kdm_path, nullptr);

KdmInfomationPtr kdmInfoPtr = nullptr;
int result = _dms_validate_kdm(kdmPath, &kdmInfoPtr);

if (result != DMS_RESULT_SUCCESS || kdmInfoPtr == nullptr) {
LOGE("KDM validation failed: 0x%08x", result);
env->ReleaseStringUTFChars(kdm_path, kdmPath);
return nullptr;
}

// Create Java KdmInfo object
jobject kdmInfo = env->NewObject(gKdmInfoClass,
                                 env->GetMethodID(gKdmInfoClass, "<init>", "()V"));

// Set fields
if (kdmInfoPtr->Id != nullptr) {
jstring id = env->NewStringUTF(kdmInfoPtr->Id);
env->SetObjectField(kdmInfo, gKdmInfo_id, id);
env->DeleteLocalRef(id);
}

if (kdmInfoPtr->RecipientSubjectName != nullptr) {
jstring recipient = env->NewStringUTF(kdmInfoPtr->RecipientSubjectName);
env->SetObjectField(kdmInfo, gKdmInfo_recipientSubjectName, recipient);
env->DeleteLocalRef(recipient);
}

if (kdmInfoPtr->CplId != nullptr) {
jstring cplId = env->NewStringUTF(kdmInfoPtr->CplId);
env->SetObjectField(kdmInfo, gKdmInfo_cplId, cplId);
env->DeleteLocalRef(cplId);
}

if (kdmInfoPtr->ContentTitle != nullptr) {
jstring contentTitle = env->NewStringUTF(kdmInfoPtr->ContentTitle);
env->SetObjectField(kdmInfo, gKdmInfo_contentTitle, contentTitle);
env->DeleteLocalRef(contentTitle);
}

if (kdmInfoPtr->NotValidBefore[0] != '\0') {
jstring notValidBefore = env->NewStringUTF(kdmInfoPtr->NotValidBefore);
env->SetObjectField(kdmInfo, gKdmInfo_notValidBefore, notValidBefore);
env->DeleteLocalRef(notValidBefore);
}

if (kdmInfoPtr->NotValidAfter[0] != '\0') {
jstring notValidAfter = env->NewStringUTF(kdmInfoPtr->NotValidAfter);
env->SetObjectField(kdmInfo, gKdmInfo_notValidAfter, notValidAfter);
env->DeleteLocalRef(notValidAfter);
}

env->SetIntField(kdmInfo, gKdmInfo_sessionCount, kdmInfoPtr->SessionCount);
env->SetIntField(kdmInfo, gKdmInfo_remainSessionCount, kdmInfoPtr->RemainSessionCount);
env->SetIntField(kdmInfo, gKdmInfo_validateTimeWindowResult, kdmInfoPtr->ValidateTimeWindowResult);
env->SetIntField(kdmInfo, gKdmInfo_validateRecipientResult, kdmInfoPtr->ValidateRecipientResult);

_dms_free_kdm_infomation(&kdmInfoPtr);
env->ReleaseStringUTFChars(kdm_path, kdmPath);

return kdmInfo;
}

JNIEXPORT jboolean JNICALL
        Java_com_yourcompany_dmsplayer_DmsPlayer_bindKdm(JNIEnv* env, jobject thiz, jstring kdm_path) {
const char* kdmPath = env->GetStringUTFChars(kdm_path, nullptr);
int result = _dms_bind_kdm(kdmPath);
env->ReleaseStringUTFChars(kdm_path, kdmPath);

return (result == DMS_RESULT_SUCCESS) ? JNI_TRUE : JNI_FALSE;
}

JNIEXPORT jobject JNICALL
        Java_com_yourcompany_dmsplayer_DmsPlayer_openMxf(JNIEnv* env, jobject thiz, jstring mxf_path) {
jclass clazz = env->GetObjectClass(thiz);
jfieldID fieldId = env->GetFieldID(clazz, "nativePtr", "J");
DmsContext* context = reinterpret_cast<DmsContext*>(env->GetLongField(thiz, fieldId));

if (context == nullptr || !context->isInitialized) {
LOGE("DMS player not initialized");
return nullptr;
}

const char* mxfPath = env->GetStringUTFChars(mxf_path, nullptr);

// For now, we'll use the DCP functions to open MXF files
// This is a temporary workaround until we have direct MXF support
int result = _dms_open_dcp(mxfPath, "dummy-session-id", false);

if (result != DMS_RESULT_SUCCESS) {
LOGE("Failed to open MXF file: 0x%08x", result);
env->ReleaseStringUTFChars(mxf_path, mxfPath);
return nullptr;
}

context->hasActiveMxf = true;
env->ReleaseStringUTFChars(mxf_path, mxfPath);

// Create Java MxfInfo object with dummy data
// In a real implementation, we would extract this from the MXF file
jobject mxfInfo = env->NewObject(gMxfInfoClass,
                                 env->GetMethodID(gMxfInfoClass, "<init>", "()V"));

env->SetIntField(mxfInfo, gMxfInfo_width, 1920);
env->SetIntField(mxfInfo, gMxfInfo_height, 1080);
env->SetFloatField(mxfInfo, gMxfInfo_frameRate, 24.0f);
env->SetLongField(mxfInfo, gMxfInfo_duration, 120 * 1000000L); // 120 seconds in microseconds
env->SetBooleanField(mxfInfo, gMxfInfo_isEncrypted, true);

jstring codec = env->NewStringUTF("JPEG2000");
env->SetObjectField(mxfInfo, gMxfInfo_codec, codec);
env->DeleteLocalRef(codec);

return mxfInfo;
}

JNIEXPORT void JNICALL
Java_com_yourcompany_dmsplayer_DmsPlayer_closeMxf(JNIEnv* env, jobject thiz) {
jclass clazz = env->GetObjectClass(thiz);
jfieldID fieldId = env->GetFieldID(clazz, "nativePtr", "J");
DmsContext* context = reinterpret_cast<DmsContext*>(env->GetLongField(thiz, fieldId));

if (context != nullptr && context->hasActiveMxf) {
_dms_close_dcp();
context->hasActiveMxf = false;
context->currentPosition = 0;
}
}

JNIEXPORT jboolean JNICALL
        Java_com_yourcompany_dmsplayer_DmsPlayer_startPlayback(JNIEnv* env, jobject thiz) {
jclass clazz = env->GetObjectClass(thiz);
jfieldID fieldId = env->GetFieldID(clazz, "nativePtr", "J");
DmsContext* context = reinterpret_cast<DmsContext*>(env->GetLongField(thiz, fieldId));

if (context == nullptr || !context->hasActiveMxf) {
LOGE("No active MXF file");
return JNI_FALSE;
}

// Reset position to start
context->currentPosition = 0;

return JNI_TRUE;
}

JNIEXPORT void JNICALL
Java_com_yourcompany_dmsplayer_DmsPlayer_stopPlayback(JNIEnv* env, jobject thiz) {
// No special action needed for now
}

JNIEXPORT jint JNICALL
        Java_com_yourcompany_dmsplayer_DmsPlayer_getNextFrame(JNIEnv* env, jobject thiz, jbyteArray buffer) {
jclass clazz = env->GetObjectClass(thiz);
jfieldID fieldId = env->GetFieldID(clazz, "nativePtr", "J");
DmsContext* context = reinterpret_cast<DmsContext*>(env->GetLongField(thiz, fieldId));

if (context == nullptr || !context->hasActiveMxf) {
LOGE("No active MXF file");
return -1;
}

jbyte* bufferPtr = env->GetByteArrayElements(buffer, nullptr);
jsize bufferLength = env->GetArrayLength(buffer);

DmsDataUnitPtr dataUnit = nullptr;
int result = _dms_get_next_picture_unit(&dataUnit);

if (result != DMS_RESULT_SUCCESS || dataUnit == nullptr) {
env->ReleaseByteArrayElements(buffer, bufferPtr, 0);
return -1; // End of stream or error
}

// Copy frame data to Java buffer
int bytesToCopy = dataUnit->Length;
if (bytesToCopy > bufferLength) {
LOGE("Buffer too small for frame data: %d > %d", bytesToCopy, bufferLength);
bytesToCopy = bufferLength;
}

memcpy(bufferPtr, dataUnit->Data, bytesToCopy);
context->currentPosition = dataUnit->Pos;

_dms_free_data_unit(&dataUnit);
env->ReleaseByteArrayElements(buffer, bufferPtr, 0);

return bytesToCopy;
}

JNIEXPORT void JNICALL
Java_com_yourcompany_dmsplayer_DmsPlayer_seekTo(JNIEnv* env, jobject thiz, jlong positionUs) {
jclass clazz = env->GetObjectClass(thiz);
jfieldID fieldId = env->GetFieldID(clazz, "nativePtr", "J");
DmsContext* context = reinterpret_cast<DmsContext*>(env->GetLongField(thiz, fieldId));

if (context == nullptr || !context->hasActiveMxf) {
LOGE("No active MXF file");
return;
}

// Convert microseconds to bytes position (this is a simplified approach)
// In a real implementation, we would need to map time to file position
int64_t targetPosition = (positionUs * 1000000) / 24; // Assuming 24fps and constant bitrate

int result = _dms_goto_pos(targetPosition, false);
if (result == DMS_RESULT_SUCCESS) {
context->currentPosition = targetPosition;
} else {
LOGE("Seek failed: 0x%08x", result);
}
}

JNIEXPORT jlong JNICALL
        Java_com_yourcompany_dmsplayer_DmsPlayer_getDuration(JNIEnv* env, jobject thiz) {
// Return duration in microseconds (2 minutes for demo)
return 120 * 1000000L;
}

JNIEXPORT jfloat JNICALL
        Java_com_yourcompany_dmsplayer_DmsPlayer_getFrameRate(JNIEnv* env, jobject thiz) {
return 24.0f;
}

JNIEXPORT jboolean JNICALL
        Java_com_yourcompany_dmsplayer_DmsPlayer_isEncrypted(JNIEnv* env, jobject thiz) {
jclass clazz = env->GetObjectClass(thiz);
jfieldID fieldId = env->GetFieldID(clazz, "nativePtr", "J");
DmsContext* context = reinterpret_cast<DmsContext*>(env->GetLongField(thiz, fieldId));

// For demo purposes, assume file is encrypted if KDM was bound
return (context != nullptr && context->hasActiveMxf) ? JNI_TRUE : JNI_FALSE;
}

} // extern "C"